package com.project.thisDatabase.base.service.impl;

import com.google.gson.reflect.TypeToken;
import com.project.annotation.data.DbTable;
import com.project.annotation.data.Symbol;
import com.project.annotation.enums.common.ConstBaseString;
import com.project.annotation.enums.data.ConditionType;
import com.project.thisCommon.common.ConvertEntity;
import com.project.thisDatabase.base.model.*;
import com.project.thisDatabase.base.model.data.Condition;
import com.project.thisDatabase.base.model.entities.IntResultEntity;
import com.project.thisDatabase.base.model.entities.PageEntity;
import com.project.thisDatabase.base.service.ContextInterfaceBase;
import com.project.thisDatabase.base.service.ServiceInterfaceBase;
import com.project.thisDatabase.base.service.impl.pack.PkConvert;
import com.project.thisDatabase.mongo.MongoBaseEntity;
import com.project.thisDatabase.mongo.MongoExecutor;
import com.project.thisDatabase.mongo.MongoPageEntity;
import com.project.thisDatabase.mongo.MongoQuery;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

public abstract class MongoServiceBase<T extends EntityBase> implements ServiceInterfaceBase<T> {

    @Autowired
    private MongoExecutor mongoExecutor;

    @Autowired
    private ContextInterfaceBase contextInterfaceBase;

    protected MongoPageEntity getPage(EntityPageBase<T> entity) {
        return getPage(entity.getPageEntity());
    }

    protected MongoPageEntity getPage(PageEntity en) {
        MongoPageEntity page = new MongoPageEntity();
        if (null != en) {
            page.setIndex(en.getPageIndex());
            page.setSize(en.getPageSize());
            Map<String, Integer> sort = new TreeMap<>();
            sort.put(
                    en.getOrderString(),
                    "DESC".equalsIgnoreCase(en.getOrderType()) ? MongoExecutor.DES : MongoExecutor.AES);
            page.setSorts(sort);
        }

        return page;
    }

    protected String getName() {
        return null;
    }

    protected String readName(T obj) {
        return StringUtils.isBlank(getName()) ? obj.getClass().getSimpleName() : getName();
    }

    public T setKeys(T obj) {
        try {
            T res = (T)ConvertEntity.copyEntity(obj, obj.getClass(), new PkConvert());
            return res;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    protected T newInstance(Object obj) {
        Type tp = ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
//        Type tp = new TypeToken<T>() {}.getType();
        try {
            T res = ConvertEntity.copyEntity(obj, tp);
            return res;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public List<T> search(T entity) {
        List<T> list = mongoExecutor.search(readName(entity), entity, null);
        return list;
    }

    @Override
    public List<T> searchPage(EntityPageBase<T> entity) {
        MongoPageEntity page = getPage(entity);
        List<T> list = null;
        try {
            T body = newInstance(entity);
            list = mongoExecutor.search(readName(body), body, page);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

    @Override
    public int update(T entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        T conditionItem = setKeys(entity);

        if(null != loginUserItem) {
            entity.setModifyBy(loginUserItem.getUserCode());
            rtn += mongoExecutor.update(readName(conditionItem), conditionItem, entity);
        }

        return rtn.intValue();
    }

    @Override
    public int updateAll(T entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        T conditionItem = setKeys(entity);

        if(null != loginUserItem) {
            entity.setModifyBy(loginUserItem.getUserCode());
            rtn += mongoExecutor.updateAll(readName(conditionItem), conditionItem, entity);
        }

        return rtn.intValue();
    }

    @Override
    public int insert(T entity) {
        Integer rtn = 0;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        if(null != loginUserItem) {
            entity.setCreateBy(loginUserItem.getUserName());
            mongoExecutor.insert(readName(entity), entity);
            rtn++;
        }

        return rtn;
    }

    @Override
    public int updateSearchList(BaseUpdateEntity<T> entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        T conditionItem = entity.getCondition();
        T contentItem = entity.getContent();

        if(null != loginUserItem) {
            contentItem.setModifyBy(loginUserItem.getUserCode());
            rtn += mongoExecutor.update(readName(conditionItem), conditionItem, contentItem);
        }

        return rtn.intValue();
    }

    @Override
    public int deleteList(BaseUpdateEntity<T> entity) {
        Long cnt = 0L;

        MongoBaseEntity<T> queryMongo = new MongoBaseEntity<>();
        queryMongo.setTableName(readName(entity.getCondition()));
        queryMongo.setStatus(ConstBaseString.YES);
        queryMongo.setBody(entity.getCondition());

        MongoBaseEntity<T> entityMongo = new MongoBaseEntity<>();
        entityMongo.setTableName(readName(entity.getCondition()));
        entityMongo.setStatus(ConstBaseString.NO);

        cnt += mongoExecutor.updateAll(readName(entity.getCondition()), queryMongo, entityMongo);
        return cnt.intValue();
    }

    @Override
    public int insertList(List<T> entity) {
        Integer rtn = 0;
        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        if(null != loginUserItem) {
            for(T en : entity){
                en.setCreateBy(loginUserItem.getUserName());
                mongoExecutor.insert(readName(en), en);
                rtn++;
            }
        }

        return rtn;
    }

    @Override
    public int updateList(List<T> entity) {
        Long rtn = 0L;
        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        if(null != loginUserItem) {
            for(T en : entity){
                T conditionItem = setKeys(en);
                en.setModifyBy(loginUserItem.getUserCode());
                rtn += mongoExecutor.update(readName(conditionItem), conditionItem, en);
            }
        }

        return rtn.intValue();
    }

    @Override
    public IntResultEntity count(T entity) {
        Long count = mongoExecutor.count(readName(entity), entity);

        IntResultEntity res = new IntResultEntity();
        res.setResult(count.intValue());
        return res;
    }

    @Override
    public int insertOrUpdate(T entity) {
        Long rtn = 0L;

        T query = setKeys(entity);
        if (null != query) {
            rtn += physicalDeleteAut(query);
            insertAut(entity);
            rtn++;
        }

        return rtn.intValue();
    }

    @Override
    public List<T> searchAut(T entity) {
        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        entity.setOwnId(loginUserItem.getId());
        List<T> list = mongoExecutor.search(readName(entity), entity, null);
        return list;
    }

    @Override
    public List<T> searchPageAut(EntityPageBase<T> entity) {
        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        entity.getData().setOwnId(loginUserItem.getId());
        MongoPageEntity page = getPage(entity);
        List<T> list = null;
        try {
            T body = newInstance(entity);
            list = mongoExecutor.search(readName(body), body, page);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

    @Override
    public int updateAut(T entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        T conditionItem = setKeys(entity);

        if(null != loginUserItem) {
            conditionItem.setOwnId(loginUserItem.getId());
            entity.setModifyBy(loginUserItem.getUserCode());
            rtn += mongoExecutor.update(readName(conditionItem), conditionItem, entity);
        }

        return rtn.intValue();
    }

    @Override
    public int insertAut(T entity) {
        Integer rtn = 0;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        entity.setOwnId(loginUserItem.getId());
        mongoExecutor.insert(readName(entity), entity);
        rtn++;

        return rtn;
    }

    @Override
    public int insertListAut(List<T> entity) {
        Integer rtn = 0;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        if(null != loginUserItem){
            for(T en : entity){
                en.setOwnId(loginUserItem.getId());
                en.setCreateBy(loginUserItem.getUserName());
                mongoExecutor.insert(readName(en), en);
                rtn++;
            }
        }

        return rtn;
    }

    @Override
    public int updateListAut(List<T> entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();

        if(null != loginUserItem) {
            for(T en : entity){
                T conditionItem = setKeys(en);
                conditionItem.setOwnId(loginUserItem.getId());
                en.setModifyBy(loginUserItem.getUserCode());
                rtn += mongoExecutor.update(readName(conditionItem), conditionItem, en);
            }
        }

        return rtn.intValue();
    }

    @Override
    public int updateSearchListAut(BaseUpdateEntity<T> entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        T conditionItem = entity.getCondition();
        T contentItem = entity.getContent();

        if(null != loginUserItem) {
            conditionItem.setOwnId(loginUserItem.getId());
            contentItem.setModifyBy(loginUserItem.getUserCode());
            rtn += mongoExecutor.update(readName(conditionItem), conditionItem, contentItem);
        }

        return rtn.intValue();
    }

    @Override
    public int deleteListAut(BaseUpdateEntity<T> entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        entity.setCondition(entity.getCondition());
        entity.getCondition().setOwnId(loginUserItem.getId());

        MongoBaseEntity<T> queryMongo = new MongoBaseEntity<>();
        queryMongo.setTableName(readName(entity.getCondition()));
        queryMongo.setStatus(ConstBaseString.YES);
        queryMongo.setBody(entity.getCondition());

        MongoBaseEntity<T> entityMongo = new MongoBaseEntity<>();
        entityMongo.setTableName(readName(entity.getCondition()));
        entityMongo.setStatus(ConstBaseString.NO);

        rtn += mongoExecutor.updateAll(readName(entity.getCondition()), queryMongo, entityMongo);

        return rtn.intValue();
    }

    @Override
    public IntResultEntity countAut(T entity) {
        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        entity.setOwnId(loginUserItem.getId());
        Long count = mongoExecutor.count(readName(entity), entity);

        IntResultEntity res = new IntResultEntity();
        res.setResult(count.intValue());
        return res;
    }

    @Override
    public int physicalDeleteAut(T entity) {
        Long rtn = 0L;

        UserInfoEntity loginUserItem = contextInterfaceBase.userInfo();
        if (null != loginUserItem) {
            entity.setOwnId(loginUserItem.getId());
            rtn += mongoExecutor.delete(readName(entity), entity);
        }

        return rtn.intValue();
    }

    @Override
    public <U> List<T> search(Condition<U> item) throws Exception {
        return search(item, null);
    }

    @Override
    public <U> List<T> search(Condition<U> item, PageEntity page) throws Exception {
        T en = newInstance(item.getBody().getClass().newInstance());
        String tableName = readName(en);
        if (item.getBody().getClass().isAnnotationPresent(DbTable.class)) {
            DbTable dbTable = item.getBody().getClass().getAnnotation(DbTable.class);
            tableName = dbTable.value();
        }

        MongoPageEntity pg = getPage(page);
        Map where = buildWhere(item);
        List<T> list = mongoExecutor.searchAllEntity(tableName, where, pg);
        return list;
    }

    @Override
    public <U> List<T> search(String[] cols, Condition<U> item, PageEntity page) throws Exception {
        return search(item, page);
    }

    @Override
    public <U> int update(T change, Condition<U> item) throws Exception {
        Map where = buildWhere(item);
        Long rtn = mongoExecutor.updateAll(readName(change), where, change);
        return rtn.intValue();
    }

    @Override
    public int change(T change) throws Exception {
        mongoExecutor.insertAll(readName(change), change);
        return 1;
    }

    @Override
    public <U> int delete(Condition<U> item) throws Exception {
        T en = newInstance(item.getBody().getClass().newInstance());
        String tableName = readName(en);
        if (item.getBody().getClass().isAnnotationPresent(DbTable.class)) {
            DbTable dbTable = item.getBody().getClass().getAnnotation(DbTable.class);
            tableName = dbTable.value();
        }

        Map where = buildWhere(item);
        Long rtn = mongoExecutor.deleteAll(tableName, where);
        return rtn.intValue();
    }

    protected Map buildWhere(Condition item) throws Exception {
        if (null == item) {
            return null;
        }

        Map cond = ConvertEntity.CovertToMap(item.getBody(), c->{
            if (null != c.getValue()) {
                if (c.getEntityCollect().getField().isAnnotationPresent(Symbol.class)) {
                    Symbol symbol = c.getEntityCollect().getField().getAnnotation(Symbol.class);
                    return new MongoQuery(symbol.type(), c.getValue());
                }
            }

            return c.getValue();
        });

        Map child = buildWhere(item.getChild());
        if (null != child) {
            if (item.getChild().getType() == ConditionType.or) {
                Map or = new HashMap();
                or.put("$or", Arrays.asList(new Object[] {cond, child}));
                return or;
            }

            child.forEach((k, v)->cond.put(k, v));
        }

        return cond;
    }
}
